//
// Program.cpp
// Application Driver.
//
// Author: Gabriel H. Barbuto (gbarbuto@dc.uba.ar)
//
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <string>

#include "chrono_io.h"

#include "CommandLineParser.h"
#include "IterativeMethod.h"
#include "MethodFactory.h"
#include "Program.h"

Program::Program(int argc, char **argv) :
	_argc(argc), _argv(argv), _epsilon(IterativeMethod::default_epsilon()), _iterations(IterativeMethod::default_iterations()), _run_count(10000)
{
}

int Program::run()
{
	int result(this->parse_command_line());

	if (result != SUCCESS)
	{
		return result;
	}

	try
	{
		std::unique_ptr<IterativeMethod> method_pointer(this->get_method());

		std::chrono::system_clock::time_point start(std::chrono::high_resolution_clock::now());

		// Execute 10000 iterations by default to make it mesurable and average the running time.
		for (std::size_t run_number(0); run_number < this->_run_count; ++run_number)
		{
			this->_root = (*method_pointer)(this->_arguments);
		}

		std::chrono::system_clock::time_point finish(std::chrono::high_resolution_clock::now());

		// We are looking for a square root of a norm. It must be positive.
		// Take the absolute value just in case the method has found the other root.
		this->_root = std::abs(this->_root);

		this->_elapsed_time = finish - start;
		this->_iterations_performed = (*method_pointer).iterations();

		this->output_results();
	}
	catch (const std::exception &e)
	{
		std::cerr << "Exception thrown: " << e.what() << std::endl;
		return -10;
	}

	return 0;
}

int Program::parse_command_line()
{
	CommandLineParser command_line_parser(*this);

	return command_line_parser.parse();
}

void Program::output_results() const
{
	std::cout << "Root: " << this->_root << std::endl;
	std::cout << "Elapsed time: " << std::chrono::duration_cast<std::chrono::nanoseconds>(this->_elapsed_time) << std::endl;
	std::cout << "Iterations performed: " << this->_iterations_performed << std::endl;
}

IterativeMethod *Program::get_method() const
{
	IterativeMethod *method(MethodFactory::instance().get(this->_function_name, this->_method_name, this->_alpha));

	method->epsilon(this->_epsilon);
	method->max_iterations(this->_iterations);

	return method;
}

// Define class constants and initialize them.
const int Program::ERROR(-1);
const int Program::SUCCESS(0);
const int Program::USAGE_SHOWN(1);
const int Program::ERROR_IN_COMMAND_LINE(2);
const int Program::ERROR_UNHANDLED_EXCEPTION(4);
